AWS再入門ブログリレーAppSync編
こんにちは、コンサルティング部のキムです!
当エントリはDevelopers.IOで弊社コンサルティング部による『AWS 再入門ブログリレー 2019』の19日目のエントリです。
昨日は中川の「AWS再入門 Amazon Elastic File System編」でした。
このブログリレーの企画は、普段AWSサービスについて最新のネタ・深い/細かいテーマを主に書き連ねてきたメンバーの手によって、 今一度初心に返って、基本的な部分を見つめ直してみよう、解説してみようというコンセプトが含まれています。
AWSをこれから学ぼう!という方にとっては文字通りの入門記事として、またすでにAWSを活用されている方にとってもAWSサービスの再発見や2019年のサービスアップデートのキャッチアップの場となればと考えておりますので、ぜひ最後までお付合い頂ければ幸いです。
では、さっそくいってみましょう。19日目のテーマは『AWS AppSync編』です。
目次
始める前に
AppSyncは割と最近出たサービスにてアップデートのスピード感も凄いサービスです。なので、まだ構築事例やベストプラクティスは少ないです。 それであくまでに本記事はAppSyncの使い方のベストプラクティスなどではなく、個人的な意見が多く含まれています。 結構長い記事になりましたので、弊社の菊池の[AWS AppSync] チュートリアル:複数データソースを組み合わせた GraphQL API の作成も読んでいただくと、より早く簡単理解できると思います。
AppSyncとは
AppSyncはAWSの管理系GraphQLサービスにて、ApolloやPrismaと比べてAWSからインフラやサーバーまで提供してくれるサービスになります。その場合私たちは何をすべきかというと以下の三つぐらいですね。
- GraphQLスキーマの定義
- リゾルバーの作成
- データソースおよびIAMロールの管理
つまり、サーバーレスの形でGraphQLのバックエンドを実装できるサービスです。AppSyncを用いなくてもAWS Lambdaなどを活かせてGraphQLのバックエンドを構築するのができましたが、この場合だとLambdaのメモリーサイズ、コールドスタート、データソースとの通信、認証ユーザーのトークン等直接開発しなければならないことがもっと多かったです。一方、AppSyncを用いる場合にはGraphQLのスキーマを作成してフィールドごとのリゾルバーを作成するだけでGraphQLのエンドポイントが生成されます。何よりも本当に便利なのはリゾルバーをVTLというテンプレート言語で作成することです。VTLに初めて接する方には少し難解に見えるかも知れませんが、VTLでリゾルバーを作成するとほとんどの作業がコピー・アンド・ペーストだけでめっちゃ便利です!(しかし、デバッグは難しいです。。。)
VTLで作成されたリゾルバーはLambdaで連携してカスタマイジングもできるんですが、それ以外にもDynamoDB、RDS、Amazon ES (Elasticsearch Service)、Http Endpoint等データソースに直接繋がるためデータハンドルが簡単です。
AppSyncを使うと便利機能がたくさんあって、バックエンドAPIを開発する時凄いスピードで開発できるところが長所です。もちろんFirebaseを用いても簡単に開発が可能ですが、Firebaseに比べてAppSyncの長所は完全なGraphQLサービスとしての優れた自由度と柔軟さにあります。
AppSync Sample Project
AppSyncは四つのサンプルプロジェクトを提供しています。本記事を読んで本気でAppSyncをいじってみようと考える方がいらっしゃったら、このサンプルを必ず分析するのを推奨します。
AppSyncの機能
AppSyncコンソルに新たなAPIを作ると下のような画面が見えます。下の画面は後本記事で説明するserverless frameworkを使ってAppSync APIを生成した後のキャプチャ画面になります。
左側に5つのメニューがあります。これからはメニューごとについて説明させていただきます!
スキーマ(Schema)
まずはスキーマ画面から始めます。スキーマ画面で提供している機能はGraphQLスキーマの作成・保存及びフィールドごとに繋がるリゾルバーの生成・照会・修正・削除になります。 言い直したらスキーマとリゾルバーの設定ですね!
右側の上から見るとCreate Resourcesというボタンがあります。このボタンを押すとスキーマで使うデータモデルのタイプ定義、DynamoDBの生成、定義されたデータタイプに関する基本的なクエリ、ミューテーション、サブスクリプションなどの定義、更にはリゾルバーまでに自動生成してくれます!
これはCreate Resourcesボタンを押した時の画面です。
Create Resourcesボタンを押して、スクロールするとこの項目も見えます。(DynamoDBのテーブル生成はもちろん、インデックスも一緒に生成できます)
リソースを生成した時の画面です(この時点でDynamoDBの生成も終わった状態です)
リゾルバー側から見える項目の中でMutation.createMyCustomTypeをクリックして自動生成されたリゾルバーを見てみましょう。
このようにAppSyncを始めてる方のためにリゾルバーやスキーマな、データソースどが自動的に設定されます。このように自動的に生成されたスキーマが動作するかどうかをテストするためには、クエリメニューに移動して確認することができますが、クエリメニューに関しては少しあとで説明します。
ここでちょっとだけ指摘したい部分があります。 AppSyncコンソールでこんなに強力な機能を提供するにもかかわらず、本番環境ではこのAppSyncコンソールを使用して実装することは推奨されません。 スキーマやリゾルバーの管理も難しいし、複数人での共同作業にも不都合があるし、いろいろ不便な点が多いからです。
このためAWSからAmplifyというCLI+SDKを提供されています。AmplifyはAppSync APIの開発する以外にもCognitoユーザーとの連携やS3バケットのファイルアップロードなどクライアント側の開発が楽になるツールです。GraphQLのスキーマを作成してAppSyncにアップロードされるスキーマやリゾルバーを自動生成したり有用な機能がたくさんあります。しかし、まだ細かい設定などは難しいところがあって、主に大学生達がアプリ開発プロジェクトを簡単にするためによく使っているそうです。
それでは実際に実務で活用できるツールは何かというと、私はserverless-appsync-pluginをオススメします。 全ての設定を一つのyamlファイルだけで出来るのでとても楽です。IaCは当然基本的にCloudFormationを使っても可能ですが、AppSyncだけではCloudFormationテンプレートを直接作成するのは推奨しません。人生が辛くなるはずです。(笑)
AppSyncの概念説明が終わったらserverless-appsync-pluginを使って簡単なAPIを実装します。その前に一旦スキーマのスカラータイプに進めます。
Scalar Types
皆さんのご存知の通りGraphQLは自動的にtype checkをやっています。例えばリクエストやレスポンスでやり取りするデータごとがIntタイプかStringタイプかを開発者がもう検知しなくていいってことです。GraphQLで定義してる一般的なスカラータイプは下のようです。
- ID
- String
- Int
- Float
- Double
AppSyncはこれに追加して下のスカラータイプを提供しています。これらを活かせてより楽に開発できます。
- AWSDate
- AWSTime
- AWSDateTime
- AWSTimestamp
- AWSEmail
- AWSJSON
- AWSURL
- AWSPhone
- AWSIPAddress
スカラータイプについてのより詳細な説明はAWSドキュメントをご参照ください。
下のコードはスカラータイプを活用した例です。
type User { id: ID! name: String! phone: AWSPhone! email: AWSEmail! myPageUrl: AWSURL! createdAt: AWSDateTime! }
Resolver
スキーマメニューからCreate Resourcesを通じて自動生成されたリゾルバーの画面からもご覧になったと思いますが、リゾルバーはVTLというJAVAベースのテンプレート言語にて作成することになっています。
このVTLファイルはマッピングテンプレート(Mapping Template)と呼ばれます。一つのリクエスト用マッピングテンプレートと一つのレスポンス用マッピングテンプレートで一つのリゾルバーを成すことになります。そのリゾルバーのマッピングテンプレートをマッピングテンプレートで呼ぶ理由は、本当にデータをマッピングするだけでいいからです。データソースに接続してコネクションを管理したりする必要なくデータをマッピングだけすれば後はAppSync側から処理してくれます。
AppSyncで使わられるリゾルバーのタイプは二つあります。
ユニットリゾルバー(Unit Resolver)
名前の通りです、で一発で説明が終わってしまうリゾルバーです。一つのデータソース(DynamoDB、RDS等)と連携させて処理するリゾルバーになります。
パイプラインリゾルバー(Pipeline Resolver)
実際にGraphQLのAPIを実装してみるとユニットリゾルバーで解決できない複雑なロジックがたくさんあります。例えば、Friendshipテーブルから二人が友達に登録された場合のみロジックを実行するとか、ポイントを使って決済する場合Pointテーブルにユーザのポイントが十分な場合のみ決済ロジックを処理するとかのいろんな状況があるかと思います。この時に有用な機能がパイプラインリゾルバーになります。
パイプラインリゾルバーは元々はユニットリゾルバーなはずの個別のリゾルバーをFunctionとして登録して使用します。このFunctionというのは他のリゾルバーからも共有されて利用出来ますので共通的なロジックをFunctionとして作っておいてから様々なパイプラインリゾルバーから使うパターンも可能です。
本記事では扱いませんが、serverless-appsync-pluginを通じても簡単にパイプラインリゾルバーを作成できます。
Data Sources
次はデータソースについて説明させていただきます。あまり内容はありませんが、とりあえずData Sourcesメニューをクリックしたらこのような画面が見えます。
これは私が予め登録しておいたデータソースです。Create Data Sourceボタンを押すとすぐ作られます。
現時点でAppSyncでデータソースにて登録可能なタイプは以下の6種類になります。
Functions
それぞれのPipeline resolverはBefore Mapping Template, Functions, After Mapping Templateによって行われます。 このとき,真ん中の部分に位置したのがFunctionになります。 一つ一つのFunctionは基本的にユニットリゾルバーとほとんど似ています. 本記事はPipeline resolverに対して詳しく扱っていないのでFunctionsについて簡単に見るだけで行きます。
まず、Functionsメニューをクリックすると下のような画面が見えます。今は一つのFunctionを登録しておいた状態です。
Create functionボタンをクリックするとデータソース及びFunctionの名前、リクエスト用マッピングテンプレートやレスポンス用マッピングテンプレートの設定が可能です。
ユニットリゾルバーからパイプラインリゾルバーに変えると、生成されたFunctionをパイプラインリゾルバーのFunctionとして登録できます。
赤のボタンを押すとユニットリゾルバ−からパイプラインリゾルバーに変更されます。
先ほど作ったFunctionを追加した場合このようになります。
パイプラインリゾルバー及びFunctionに関してより詳細な内容はAWS ドキュメントをご参照できればと思います。
クエリ(Queries)
クエリメニューはGraphQLのPlaygroundみたいな機能を提供します。Cognitoなどを使ってログインした状態でクエリを実行してみたり、ログアウトした状態でクエリを実行してみたりするのができます。
左側がリクエスト、右側はレスポンスです。
設定(Settings)
SettingsメニューはAPI情報(エンドポイントURL、API ID、API Key)や認証方法(API Key、Cognito、IAM、OpenID)やロギングなどを設定するのが可能です。 Settingsに関してはあまり直観的すぎてこれ以上説明が要らないと思います。(笑)
それでは、これからは簡単なチュートリアルを通じてserverless-appsync-pluginを活用してAppSyncのプロジェクトをどうやって作るのかを紹介します。
AppSync Tutorial with Serverless Framework
ここではServerless Frameworkのserverless-appsync-pluginを活かせて簡単なAppSyncプロジェクトを構築してみます。 本チュートリアルで使うデータモデルは以下の三つです。
type User id: ID! name: String! email: AWSEmail! posts: [Post!]! createdAt: AWSDateTime! } type Post { id: ID! user: User! title: String! content: String! likes: [Like!]! createdAt: AWSDateTime! } type Like { id: ID! user: User! post: Post! createdAt: AWSDateTime! }
テストのシナリオは以下の通りです。
- 新規ユーザーの生成
- 新規ポストの作成
- ポストに『いいね』を付ける
- 新規ポストが生成されたらサブスクリプションを通じて伝達
- 特定のポストに『いいね』が付いたらサブスクリプションを通じて伝達
本チュートリアルのコードはGithub Repositoryでご確認ください。
プロジェクトの初期セッティング
本チュートリアルを実行するためには以下のツールのインストールが必要になります。
- yarn または npm
- Serverless
私が使っているツールのバージョンはこれです。
プロジェクトのディレクトリを構成します。 私はappsync-tutorialという名前のディレクトリから作業します。
mkdir appsync-tutorial cd appsync-tutorial touch serverless.yml mkdir schema mkdir resolvers
とりあえずserverless-appsync-pluginを予めインストールしておきましょう
yarn add serverless-appsync-plugin
最後にDone in 10.12sみたいに出力されたら完了です。
Schema
今回はスキーマを作成します。 serverless-appsync-pluginにはSchema stitchingという機能を提供していますので、スキーマをModuleごとに分けて作成できます。
cd schema touch user.graphql touch post.graphql touch like.graphql
三つのスキーマが用意されたら以下のようにスキーマを作成しましょう。
user.graphql
type User { userId: ID! name: String! email: AWSEmail! posts: [Post!]! createdAt: AWSDateTime! } input CreateInputUser { name: String! email: AWSEmail! } type Query { listUser: [User!]! getUser(userId: ID!): User } type Mutation { createUser(input: CreateInputUser!): User }
ユーザータイプは名前とEメールをインプットとして生成し、idやcreatedAtフィールドはリゾルバーにて自動生成します。 またpostsフィールドの場合は他のフィールドとは異なり、UserテーブルではなくPostテーブルからデータをもたらします。この部分に関してはserverless.ymlファイルを作成する際に改めて説明します。
post.graphql
type Post { postId: ID! user: User! title: String! content: String! likes: [Like!]! createdAt: AWSDateTime! } input CreatePostInput { userId: ID! title: String! content: String! } type Query { listPost: [Post!]! listPostByUser(userId: ID!): [Post!]! getPost(postId: ID!): Post } type Mutation { createPost(input: CreatePostInput!): Post } type Subscription { onNewPostCreated: Post @aws_subscribe(mutations: ["createPost"]) }
ポストタイプはユーザーと比べてサブスクリプション(Subscription)が登場しました。サブスクリプション(Subscription)はAppSyncのミューテーション(Mutation)が実行された際、クライアント側にそのデータを伝達してくれます。本チュートリアルはcreatePostが実行されたらonNewPostCreatedというサブスクリプションを登録したクライアントにリアルタイムでそのデータを伝達するシナリオを検証します。AppSyncのサブスクリプションはWebSocket上でMQTTプロトコルで行われっています。より詳細な内容はAWSドキュメントをご覧ください。
like.graphql
type Like { likeId: ID! userId: ID! postId: ID! createdAt: AWSDateTime! } type Query { listLike(postId: ID!): [Like!]! } type Mutation { likePost(userId: ID!, postId: ID!): Like cancelLikePost(likeId: ID!): Like } type Subscription { onPostLiked(postId: ID!): Like @aws_subscribe(mutations: ["likePost"]) onPostLikeCanceled(postId: ID!): Like @aws_subscribe(mutations: ["cancelLikePost"]) }
最後にライクタイプです。ライクタイプはサブスクリプションがポストタイプと異なり、特定なポストIDについてのサブスクリプションのみが得られます。 それ以外にもページネーションなど複雑なクエリのためのフィルターなどの設定も可能になりますが、本チュートリアルはあくまでAppSyncをより直観的に理解できるようにするためそこまでの設定はしていません。 AppSyncが提供しているサンプルプロジェクトではこのような様々なパターンを学べますのでご参照くださいませ。
Resolvers (Mapping Template)
次はresolversです。 かなり多くのリゾルバーファイルを作らなければいけません。見方によっては一番面倒な作業であるかもしれませんが、実はこれはAmplifyを活用すれば必要なリゾルバーファイルやコードが自動的に生成されてとても便利です。Amplifyは必要に応じて適切に活用しましょう。しかし本チュートリアルではシンプルに説明するため一つずつ全部作ってみます。この時、ファイル名が結構重要になります。serverless-appsync-pluginからdefaultに認識するファイル名がありますので、できれば以下のようなルールを守りましょう。
{type}.{field}.request.vtl {type}.{field}.respose.vtl
スカラータイプやサブスクリプションに関してのリゾルバーは要らないです。
cd ../resolvers # User touch User.posts.response.vtl touch User.posts.request.vtl touch Query.getUser.request.vtl touch Query.getUser.response.vtl touch Query.listUser.request.vtl touch Query.listUser.response.vtl touch Mutation.createUser.request.vtl touch Mutation.createUser.response.vtl # Post touch Post.user.request.vtl touch Post.user.response.vtl touch Post.likes.request.vtl touch Post.likes.response.vtl touch Query.getPost.request.vtl touch Query.getPost.response.vtl touch Query.listPost.request.vtl touch Query.listPost.response.vtl touch Query.listPostByUser.request.vtl touch Query.listPostByUser.response.vtl touch Mutation.createPost.request.vtl touch Mutation.createPost.response.vtl # Like touch Query.listLike.request.vtl touch Query.listLike.response.vtl touch Mutation.likePost.request.vtl touch Mutation.likePost.response.vtl touch Mutation.cancelLikePost.request.vtl touch Mutation.cancelLikePost.response.vtl
かなり多くのファイルができてしまいました。これらをいつ全部作成するのって思っている方もいらっしゃるかもしれませんが、実はこのリゾルバー、ほぼコピー・アンド・ペーストです。完成されたリゾルバーはGithub Repositoryをご確認ください。
ここではこの中でいくつかだけ見てみます。
Mutation.createUser.request.vtl
$util.qr($context.args.input.put("createdAt", $util.defaultIfNull($ctx.args.input.createdAt, $util.time.nowISO8601()))) { "version": "2017-02-28", "operation": "PutItem", "key": { "userId": $util.dynamodb.toDynamoDBJson($util.defaultIfNullOrBlank($ctx.args.input.userId, $util.autoId())) }, "attributeValues": $util.dynamodb.toMapValuesJson($context.args.input), "condition": { "expression": "attribute_not_exists(#userId)", "expressionNames": { "#userId": "userId" } } }
Mutation.createUser.response.vtl
$util.toJson($context.result)
VTLの文法を初めてご覧になった方だとコレなに?って感じるかもしれませんが、AppSyncで使うVTLの説明に関しましてはAWSドキュメント上でやさしく説明されており本当に分かりやすいです。
ここには文法抜きでコードの流れの観点で説明させていただきます。
最初はそれぞれのリゾルバーまたはマッピングテンプレートを登録する時どのデータソースと繋がるかを選択します。本チュートリアルで使っているserverless-appsync-pluginを利用する場合にはserverless.ymlファイルに作成します。(少し後で説明させていただきます)
なのでマッピングテンプレートを読む際はデータソースがもう決まっている状態になります。Mutation.createUser.request.vtl及びMutation.createUser.response.vtlではDynamoDBのUserテーブルと連携されています。
"version"はいつでもこのままで"2017-02-28"となります。 "operation"はDynamoDBだとcreateUserに該当するoperationが"PutItem"になります。 以降のフィールドに関しましてはどんなoperationに該当するかによって異なりますが、ここのDynamoDBのPutItemはKeyとAttributeを要求するため上記のように作成します。
$util、$context(または $ctx)、$arguments (または $args) 等はAppSyncからリゾルバー作成のため提供しているオブジェクトにてContext Reference及びUtility Referenceページから詳細な内容を確認できます。
User.posts.request.vtl
{ "version" : "2017-02-28", "operation" : "Query", "index" : "userId-index", "query" : { "expression": "userId = :userId", "expressionValues" : { ":userId" : $util.dynamodb.toDynamoDBJson($ctx.source.userId) } } }
User.posts.response.vtl
$util.toJson($context.result.items)
Userタイプにはpostsというフィールドがありました。このフィールドだけはUserテーブルではなくPostテーブルから持します。 User.posts.request.vtlを見ると"index"という項目があります。User.posts.request.vtlやUser.posts.response.vtlのデータソースはDynamoDBのPostテーブルに設定しておき、このPostテーブルのGSI(Global Secondary Index)、つまりここでは userId-indexを参照して該当するuserIdによる全てのPostをリストで持たせることになります。このGSIもserverless.ymlから定義できます。
serverless.yml
次は上の全てリソースをyamlファイルにて管理するserverless.ymlについて説明させていただきます。
service: classmethod-appsync-tutorial frameworkVersion: ">=1.48.0 <2.0.0" provider: name: aws runtime: nodejs10.x stage: dev region: ap-northeast-2 plugins: - serverless-appsync-plugin custom: appSync: name: AppSyncTutorialByClassmethod authenticationType: AMAZON_COGNITO_USER_POOLS userPoolConfig: awsRegion: ap-northeast-2 defaultAction: ALLOW userPoolId: { Ref: AppSyncTutorialUserPool } region: ap-northeast-2 mappingTemplatesLocation: resolvers mappingTemplates: # User - type: User field: posts dataSource: Post - type: Query field: listUser dataSource: User - type: Query field: getUser dataSource: User - type: Mutation field: createUser dataSource: User # Post - type: Post field: user dataSource: User - type: Post field: likes dataSource: Like - type: Query field: listPost dataSource: Post - type: Query field: listPostByUser dataSource: Post - type: Query field: getPost dataSource: Post - type: Mutation field: createPost dataSource: Post # Like - type: Query field: listLike dataSource: Like - type: Mutation field: likePost dataSource: Like - type: Mutation field: cancelLikePost dataSource: Like schema: - schema/user.graphql - schema/post.graphql - schema/like.graphql #serviceRole: # if not provided, a default role is generated dataSources: - type: AMAZON_DYNAMODB name: User description: User Table config: tableName: User iamRoleStatements: - Effect: Allow Action: - dynamodb:* Resource: - arn:aws:dynamodb:${self:provider.region}:*:table/User - arn:aws:dynamodb:${self:provider.region}:*:table/User/* - type: AMAZON_DYNAMODB name: Post description: Post Table config: tableName: Post iamRoleStatements: - Effect: Allow Action: - dynamodb:* Resource: - arn:aws:dynamodb:${self:provider.region}:*:table/Post - arn:aws:dynamodb:${self:provider.region}:*:table/Post/* - type: AMAZON_DYNAMODB name: Like description: Like Table config: tableName: Like iamRoleStatements: - Effect: Allow Action: - dynamodb:* Resource: - arn:aws:dynamodb:${self:provider.region}:*:table/Like - arn:aws:dynamodb:${self:provider.region}:*:table/Like/* resources: Resources: AppSyncTutorialUserPool: Type: AWS::Cognito::UserPool DeletionPolicy: Retain Properties: UserPoolName: AppSyncTutorialUserPool AutoVerifiedAttributes: - email Policies: PasswordPolicy: MinimumLength: 8 UsernameAttributes: - email AppSyncTutorialUserPoolWebClient: Type: AWS::Cognito::UserPoolClient Properties: ClientName: Web GenerateSecret: false RefreshTokenValidity: 30 UserPoolId: { Ref: AppSyncTutorialUserPool } UserTable: Type: AWS::DynamoDB::Table Properties: TableName: User KeySchema: - AttributeName: userId KeyType: HASH AttributeDefinitions: - AttributeName: userId AttributeType: S BillingMode: PAY_PER_REQUEST PostTable: Type: AWS::DynamoDB::Table Properties: TableName: Post KeySchema: - AttributeName: postId KeyType: HASH AttributeDefinitions: - AttributeName: postId AttributeType: S - AttributeName: userId AttributeType: S BillingMode: PAY_PER_REQUEST # GSI - userId GlobalSecondaryIndexes: - IndexName: userId-index KeySchema: - AttributeName: userId KeyType: HASH - AttributeName: postId KeyType: RANGE Projection: ProjectionType: ALL LikeTable: Type: AWS::DynamoDB::Table Properties: TableName: Like KeySchema: - AttributeName: likeId KeyType: HASH AttributeDefinitions: - AttributeName: likeId AttributeType: S - AttributeName: userId AttributeType: S - AttributeName: postId AttributeType: S BillingMode: PAY_PER_REQUEST GlobalSecondaryIndexes: # GSI - userId - IndexName: userId-index KeySchema: - AttributeName: userId KeyType: HASH - AttributeName: likeId KeyType: RANGE Projection: ProjectionType: ALL # GSI - postId - IndexName: postId-index KeySchema: - AttributeName: postId KeyType: HASH - AttributeName: likeId KeyType: RANGE Projection: ProjectionType: ALL
使っているserverless frameworkの"frameworkVersion"が異なる場合必ずこの値を変えてください。 "provider"にて"stage"や"region"等を設定してCloudFormationが動作するregionを決めます。serverless.ymlファイル一つに当たりCloudFormation スタック一つになります。 "plugins"は"serverless-appsync-plugin"を入力します。
"custom"の"appSync"の下位に作成されたものをserverless-appsync-pluginで読み込んで処理することになります。
一つずつ見てみると、"authenticationType"の場合API KeyやCognitoなどが設定できますが、本テュートリアルではCognito User Poolを生成して作業します。"userPoolConfig"の下にある"userPoolId"の"Ref"は、"custom"項目下の"resources"のAWS:Cognito:UserPoolのUserPoolNameと一致しなければなりません。
また"appSync"の"region"を見てください。この項目は直接入力しなくても自動的に"provider"の"region"に設定されます。 "mappingTemplatesLocation"はVTLファイルが位置されたディレクトリの経路を意味します。ディフォルトでmaping-templatesが設定されていますが本テュートリアルではresolversというフォルダーの下にVTLファイルを置いたのでresolversと書いておきます。
次は"mappingTemplates"です。
"type"はGraphQLスキーマのtypeを意味します。 Userタイプの場合はpostsというPostタイプのフィールドがありましたが、postsはスカラータイプではない為、このフィールドに該当する別のリゾルバーが必要になります。
# User - type: User field: posts dataSource: Post
もう一つの例を挙げるとlistUserの場合typeがQueryでした。listUserが接続されるデータソースはDynamoDBのUserテーブルですので下のようになります。
- type: Query field: listUser dataSource: User
ここでリクエストやレスポンス用のマッピングテンプレートのファイル名は書いてありません。別に"request"や"response"という項目にファイル名を書いてもいいですが、上記でも述べたようにserverless-appsync-pluginではVTLファイル名のコンベンションにより
request: {type}.{field}.request.vtl response: {type}.{field}.response.vtl
をデフォルト値として決められています。そのコンベンションに従い本チュートリアルではrequestやresponse項目はなしで進めます。
"appSync"の下位の"dataSource"は実際にテーブルを生成するものではなく、既存テーブルを参照してこのテーブルについてのIAM Roleを生成する役割があります。実際にテーブルを作るのは"custom"の下の"resources"で行います。
ここでは IAM RoleのActionを以下のように設定しましたが、実際のプロジェクトの時は必要な権限のみ含めたほうが推奨です。
Action: - dynamodb:*
最後に"resources"項目です。
"resources"項目にはCognito User Pool及びDynamoDBのテーブルの生成を担当しています。 DynamoDBのGSI(Global Secondary Index)もここで生成します。
デプロイ
ここまで完了しましたらディプロイは簡単です。
serverless deploy -v
上記のコマンドを入力したらCloudFormationを通じてディプロイが始まります。slsはserverlessのaliasです。
テストしてみよう(Queryメニュー)
これからはAWSコンソールに接続して上記のテストシナリオを確認してみます。
その前にCognito Userを作りましょう。Cognitoのサービスに移動します。下の画面でManage User Poolボタンをクリックします。
先ほどのディプロイから生成されたUserPoolをクリックします。
左側のメニューから Users and Groups を選択します。
Create User ボタンをクリックします。
serverless.ymlでCognito User Poolを生成した時設定した通りユーザを作ります。
Properties: UserPoolName: AppSyncTutorialUserPool AutoVerifiedAttributes: - email Policies: PasswordPolicy: MinimumLength: 8 UsernameAttributes: - email
本チュートリアルではemailをユーザ名として使います。パスワードはただ8字以上であればOKです。
Cognito ユーザが生成されました!
もう一つ、App Client IDを確認する為 App Client Settings のメニューをクリックします。
ここで Client ID を確認してコピーしておきます。
いよいよ AppSyncコンソルに入ります。Queryメニューをクリックして下のような画面に移動します。
クエリを実行する前にログインが必要なのでログインボタンをクリックしてログインしましょう。
最初のログインをしたらパスワードの変更が必要です。さっさと変更します。
まずはユーザから作ってみます。左側のようなミューテーションを書いて実行してみると右側の結果が出してきます。
次はポストを作ってみます。
新しいユーザとポストを作った後、リストクエリを実行します。
今度は特定のユーザのポストのみをリストで持たすlistPostByUserクエリの実行結果です。予想した通りよく動いています。
次はサブスクリプションがよく動いているのかどうかを確認する為、もう一つのウェブブラウザーを用意します。 まずは新しいポストが生成された際にサブスクリプションが動作するかを確認してみます。
右側のブラウザーではサブスクリプションを実行して新たなデータが生成されるのをお待ちしています。 左側のミューテーションを実行すると下の画面のように右側に結果が伝達されたのを確認できます。
はい、よく動いてます!
最後に特定なポストのいいねの生成のみをサブスクリプションできるかどうかも確認してみます。 左側のミューテーションの実行をお待ちしている画面です。
左側のpostIdと右側のpostIdが異なっていますので、予想される結果は右側に何も伝達されないってことです。 左側のミューテーションを実行したら左側の結果は出てきましたが、30秒ほど待っていても右側は何も起こっていませんでした。
今回は同じpostIdで設定してみます。
結果は成功でした。
テストは以上になりますが、まだ残っているミューテーションやクエリなどはGithub Repositoryの完成されているコードから試してください。
最後に
以上、AWS再入門ブログリレー 19日目のエントリ『AWS AppSync』編でした。
AppSyncは2018年4月にGAされて、2018年11月にパイプラインリゾルバーやAurora Serverlessサポートなどのメイジャーアップデートを続いてきました。割と最近リリースされたサービスですので、まだ使い方やベストプラクティスなどはよく知れていません。でも実際にAppSyncをいじってみた結果、思ったよりすごく便利なサービスだったことが分かりました。本記事がAppSyncに入門しようといらっしゃる方へ少しでも役に立ったらとても幸いです。
次のブログリレー(7/29)は川原の「Amazon RDS」編です。お楽しみに!!